import direct.directbase.DirectStart # starts Panda
from pandac.PandaModules import *    # basic Panda modules
from direct.showbase.DirectObject import DirectObject  # for event handling
from direct.actor.Actor import Actor # for animated models
from direct.interval.IntervalGlobal import *  # for compound intervals
from direct.task import Task         # for update fuctions
import sys, math, random
#from FollowCam import FollowCam

class World(DirectObject): # subclassing here is necessary to accept event
    def __init__(self):
        # turn off default mouse control, otherwise can't reposition camera
        base.disableMouse()
        camera.setPosHpr(0, -15, 7, 0, -15, 0)
        self.loadModels()
        self.setupLights()
        self.setupCollisions()
        render.setShaderAuto() # turns on per-pixel lighting, normal mapping, etc (you probably want to use this)
        self.keyMap = {"left":0, "right":0, "forward":0}
        taskMgr.add(self.move, "moveTask")
        self.prevtime = 0
        self.isMoving = False
                
        self.accept("escape", sys.exit)
        self.accept("arrow_up", self.setKey, ["forward", 1])
        self.accept("arrow_right", self.setKey, ["right", 1])
        self.accept("arrow_left", self.setKey, ["left", 1])
        self.accept("arrow_up-up", self.setKey, ["forward", 0])
        self.accept("arrow_right-up", self.setKey, ["right", 0])
        self.accept("arrow_left-up", self.setKey, ["left", 0])
        self.accept("ate-smiley", self.eat)
        # "mouse1" is the event when the left mouse button is clicked
        # append "-up" for the equivalent of a pygame.KEYUP event
        
                # don't use -repeat because of slight delay after keydown
        self.pandaWalk = False
        self.pandaReverse = False
        self.pandaLeft = False
        self.pandaRight = False

        self.accept("escape", exit)
        self.accept("w", self.beginWalk)
        self.accept("w-up", self.endWalk)
        self.accept("s", self.beginReverse)
        self.accept("s-up", self.endReverse)
        self.accept("a", self.beginTurnLeft)
        self.accept("a-up", self.endTurnLeft)
        self.accept("d", self.beginTurnRight)
        self.accept("d-up", self.endTurnRight)

        taskMgr.add(self.updatePanda, "update panda")

    def resetMouse(self):
        cx = base.win.getProperties().getXSize() / 2
        cy = base.win.getProperties().getYSize() / 2
        base.win.movePointer(0, cx, cy)

    def beginWalk(self):
        self.panda.setPlayRate(1.0, "walk")
        self.panda.loop("walk")
        self.pandaWalk = True

    def endWalk(self):
        self.panda.stop()
        self.pandaWalk = False

    def beginReverse(self):
        self.panda.setPlayRate(-1.0, "walk")
        self.panda.loop("walk")
        self.pandaReverse = True

    def endReverse(self):
        self.panda.stop()
        self.pandaReverse = False

    def beginTurnLeft(self):
        self.pandaLeft = True

    def endTurnLeft(self):
        self.pandaLeft = False

    def beginTurnRight(self):
        self.pandaRight = True

    def endTurnRight(self):
        self.pandaRight = False

    def updatePanda(self, task):
        if base.mouseWatcherNode.hasMouse():
            self.panda.setH(self.panda, -base.mouseWatcherNode.getMouseX() * 10)

        self.resetMouse()

        if self.pandaWalk:
            self.panda.setY(self.panda, -0.2)
        elif self.pandaReverse:
            self.panda.setY(self.panda, 0.2)

        if self.pandaLeft:
            self.panda.setH(self.panda, 0.8)
        elif self.pandaRight:
            self.panda.setH(self.panda, -0.8)

        return task.cont
        
    def setKey(self, key, value):
        self.keyMap[key] = value
        
    def loadModels(self):
        """loads initial models into the world"""
        self.panda = Actor("models/panda-model", {"walk":"panda-walk4", "eat":"panda-eat"})
        self.panda.reparentTo(render)
        self.panda.setScale(0.005)
        self.panda.setH(180)
        self.env = loader.loadModel("models/environment")
        self.env.reparentTo(render)
        self.env.setScale(0.25)
        self.env.setPos(-8, 42, 0)
        
        # load targets
        self.targets = []
        for i in range(10):
            target = loader.loadModel("smiley")
            target.setScale(0.5)
            target.setPos(random.uniform(-20,20), random.uniform(-15,15), 2)
            target.reparentTo(render)
            self.targets.append(target)
        
    def setupLights(self):
        # ambient light
        self.ambientLight = AmbientLight("ambientLight")
        # four values, RGBA (alpha is largely irrelevant), value range is 0:1
        self.ambientLight.setColor((0.25, 0.25, 0.25, 1))
        self.ambientLightNP = render.attachNewNode(self.ambientLight)
        # the nodepath that calls setLight is what gets illuminated by the light
        render.setLight(self.ambientLightNP)
        # call clearLight() to turn it off
        
        self.keyLight = DirectionalLight("keyLight")
        self.keyLight.setColor((0.6, 0.6, 0.6, 1))
        self.keyLightNP = render.attachNewNode(self.keyLight)
        self.keyLightNP.setHpr(0, -26, 0)
        render.setLight(self.keyLightNP)
        
        self.fillLight = DirectionalLight("fillLight")
        self.fillLight.setColor((0.4, 0.4, 0.4, 1))
        self.fillLightNP = render.attachNewNode(self.fillLight)
        self.fillLightNP.setHpr(30, 0, 0)
        render.setLight(self.fillLightNP)
        
    def walk(self):
        """compound interval for walking"""
        dist = 5
        angle = deg2Rad(self.panda.getH())
        dx = dist * math.sin(angle)
        dy = dist * -math.cos(angle)
        pandaWalk = Parallel(self.panda.posInterval(1, (self.panda.getX() + dx, \
            self.panda.getY() + dy, 0)),
            self.panda.actorInterval("walk", loop = 1, duration = 1))
        pandaWalk.start()
        # some interval methods:
        #   start(), loop(), pause(), resume(), finish()
        #       start() can take arguments: start(starttime, endtime, playrate)
    
    def turn(self, direction):
        pandaTurn = self.panda.hprInterval(0.2, (self.panda.getH() - direction * 10, 0, 0)) 
        pandaTurn.start()
        
    def move(self, task):
        elapsed = task.time -self.prevtime
        camera.lookAt(self.panda)
        if self.keyMap["left"]:
            self.panda.setH(self.panda.getH() + elapsed * 100)
        if self.keyMap["right"]:
            self.panda.setH(self.panda.getH() - elapsed * 100)
        if self.keyMap["forward"]:
            dist = 10 * elapsed
            angle = deg2Rad(self.panda.getH())
            dx = dist * math.sin(angle)
            dy = dist * -math.cos(angle)
            self.panda.setPos(self.panda.getX() + dx, self.panda.getY() + dy, 0)
            
        if self.keyMap["left"] or self.keyMap["right"] or self.keyMap["forward"]:
            if self.isMoving == False:
                self.isMoving = True
                self.panda.loop("walk")
        else:
            if self.isMoving:
                self.isMoving = False
                self.panda.stop()
                self.panda.pose("walk", 4)
        
        self.prevtime = task.time
        return Task.cont
        
    def setupCollisions(self):
        # instantiates a collision traverser and sets it to the default
        base.cTrav = CollisionTraverser()
        self.cHandler = CollisionHandlerEvent()
        # set pattern for event sent on collision
        #   "%in" is substituted with the name of the intro object
        self.cHandler.setInPattern("ate-%in")
        
        cSphere = CollisionSphere((0, 0, 0), 500) # 500 because the panda is scaled way down
        cNode = CollisionNode("panda")
        cNode.addSolid(cSphere)
        cNode.setIntoCollideMask(BitMask32.allOff()) # panda is *only* a from object (optional here)
        cNodePath = self.panda.attachNewNode(cNode)
        #cNodePath.show()
        # registers a from object with the traverser with a corresponding handler
        base.cTrav.addCollider(cNodePath, self.cHandler)
        
        for target in self.targets:
            cSphere = CollisionSphere((0, 0, 0,), 2)
            cNode = CollisionNode("smiley")
            cNode.addSolid(cSphere)
            cNodePath = target.attachNewNode(cNode)
            #cNodePath.show()
        
    def eat(self, cEntry):
        """handles the panda eating a smiley"""
        # remove target from list of targets
        self.targets.remove(cEntry.getIntoNodePath().getParent())
        # remove from scene graph
        cEntry.getIntoNodePath().getParent().remove()
        self.panda.play("eat")
        
w = World()
run()















